package com.skillworld.webapp.model.tutorialservice;

import java.util.Calendar;
import java.util.List;
import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.skillworld.webapp.model.award.AwardDao;
import com.skillworld.webapp.model.complaint.Complaint;
import com.skillworld.webapp.model.complaint.Complaint.Type;
import com.skillworld.webapp.model.complaint.ComplaintDao;
import com.skillworld.webapp.model.department.Department;
import com.skillworld.webapp.model.department.DepartmentDao;
import com.skillworld.webapp.model.language.LanguageNotValidException;
import com.skillworld.webapp.model.languageservice.LanguageService;
import com.skillworld.webapp.model.tutorial.MarkFilter;
import com.skillworld.webapp.model.tutorial.MarkedTutorial;
import com.skillworld.webapp.model.tutorial.MarkedTutorial.Mark;
import com.skillworld.webapp.model.tutorial.MarkedTutorialDao;
import com.skillworld.webapp.model.tutorial.Tutorial;
import com.skillworld.webapp.model.tutorial.Tutorial.Status;
import com.skillworld.webapp.model.tutorial.TutorialDao;
import com.skillworld.webapp.model.tutorial.TutorialOrder;
import com.skillworld.webapp.model.user.User;
import com.skillworld.webapp.model.user.UserDao;
import com.skillworld.webapp.model.util.AssignAward;
import com.skillworld.webapp.model.util.AssignPoints;
import com.skillworld.webapp.model.util.InvalidArgumentException;
import com.skillworld.webapp.model.util.PermissionDeniedException;

import es.udc.pojo.modelutil.exceptions.InstanceNotFoundException;


@Service("TutorialService")
@Transactional
public class TutorialServiceImpl implements TutorialService
{
    public static final int MAX_COUNT = 100;

    @Autowired
	private TutorialDao tutorialDao;
    
    @Autowired
	private MarkedTutorialDao markedTutorialDao;
    
    @Autowired
    private UserDao userDao;
    
    @Autowired
    private AwardDao awardDao;
    
    @Autowired
    private ComplaintDao complaintDao;
    
    @Autowired
    private DepartmentDao departmentDao;
    
    @Autowired
    private LanguageService languageService;
    

	@Transactional(readOnly=true)
	public List<Tutorial> searchTutorials(String query,
	                                Long dept, Integer duration, Locale locale,
	                                List<Status> status,
	                                Long author, List<Long> nonAuthors,
	                                MarkFilter marked, 
	                                Long notInCourse, 
	                                TutorialOrder order, 
	                                int startIndex, int count)
	{
	    List<String> keywords = Query.parseQuery(query);
		return tutorialDao.search(keywords,
		                            dept, duration, locale, status,
		                            author, nonAuthors, marked,
		                            notInCourse, order, startIndex, count);
	}
	
	@Transactional(readOnly=true)
    public int numberOfTutorials(String query,Long dept, 
                Integer duration, Locale locale, List<Status> status, 
                Long author, List<Long> nonAuthors, 
                MarkFilter marked, Long notInCourse)
	{
	    List<String> keywords = Query.parseQuery(query);
	    return(int) tutorialDao.numberOfTutorials(keywords,
	                                dept, duration, locale, status, 
	                                author, nonAuthors, 
	                                marked, notInCourse);
    }
	
	// WARNING: This is not read-only, note the @t.newView()@ !
	public Tutorial findTutorial(Long tutorialId) throws InstanceNotFoundException
	{
	    Tutorial t = tutorialDao.findById(tutorialId);
	    t.newView();
	    return t;
	}
	
	@Transactional(readOnly=true)
	public List<Mark> findTutorialMarks(long userId, long tutorialId)
	{
	    return markedTutorialDao.findTutorialMarks(userId, tutorialId);
	}

	@Transactional(readOnly=true)
    public List<Tutorial> findTutorialVersions(long tutorialId)
            throws InstanceNotFoundException
    {   
	    Tutorial t = tutorialDao.findById(tutorialId);
	    
	    if (t.isDerived())
	    { return tutorialDao.findTutorialVersions(t.getOriginal().getTutorialId()); }
	    else
	    { return tutorialDao.findTutorialVersions(tutorialId); }
    }
	
    public TutorialInfo viewTutorial(long userId, long tutorialId)
            throws InstanceNotFoundException
    {
        Tutorial tutorial = tutorialDao.findById(tutorialId);
        List<MarkedTutorial> marks = markedTutorialDao.findMarks(userId, tutorialId);
        return new TutorialInfo(tutorial,marks);
    }
        
	// TODO Extract fields from XML here
	// TODO Check if locale is supported
	public Tutorial createTutorial(long userId, long departmentId, 
	                                Long originalId, String xml)
	        throws InstanceNotFoundException, InvalidTutorialFormat
	{
		User author = userDao.findById(userId);
		Department department = departmentDao.findById(departmentId);
		Tutorial original = (originalId != null)
		                    ? tutorialDao.findById(originalId)
		                    : null;
		
		TutorialDom dom = new TutorialDom(xml);
		                    
	    Tutorial tutorial = null;
	        tutorial = new Tutorial(dom.getTitle(), dom.getSummary(), 
	                                department, 
	                                dom.getDifficulty(), dom.getDuration(), 
	                                dom.getLocale(), author, xml, original);
	    tutorialDao.save(tutorial);
		
	    return tutorial;
	}

	// TODO Check that translation is consistent with the original version
	public Tutorial translateTutorial(long userId, long tutorialId, 
	                                    String xml)
	        throws InstanceNotFoundException, PermissionDeniedException,
	                TranslationAlreadyExistsException, InvalidTutorialFormat
	{
	    
	    Tutorial original = tutorialDao.findById(tutorialId);
	    TutorialDom dom = new TutorialDom(xml);
	    Locale locale = dom.getLocale();
	    
        if(original.isPending() || original.isRejected())
        { throw new PermissionDeniedException(); }	    
	    
	    if(tutorialDao.existsTranslation(tutorialId, locale))
	    { throw new TranslationAlreadyExistsException(tutorialId, locale); }
	    
	    return createTutorial(userId, 
	                    original.getDepartment().getDepartmentId(), 
	                    tutorialId, xml);
    }

	/**
	 * @rate@ is in [1-5]
	 * */
	public void rateTutorial(Long userId, Long tutorialId, int rate)
	        throws InstanceNotFoundException, PermissionDeniedException
	{
	    User user = userDao.findById(userId);
        Tutorial tutorial = tutorialDao.findById(tutorialId);
        List<Mark> tutorialMarks = markedTutorialDao.findTutorialMarks(userId, tutorialId);
        
        if(tutorial.isPending() || tutorial.isRejected())
        { throw new PermissionDeniedException(); }
        
        if(tutorial.getAuthor() == user)
        { throw new PermissionDeniedException(); }
        
        if(tutorialMarks.contains(Mark.RATED))
        { throw new PermissionDeniedException(); }
        
        if(!tutorialMarks.contains(Mark.DONE))
        { throw new PermissionDeniedException(); }

        tutorial.rate(rate);
        
        MarkedTutorial tutorialMark = 
            new MarkedTutorial(user, tutorial, Mark.RATED, rate);
        markedTutorialDao.save(tutorialMark);
        
        new AssignPoints(tutorial)
                    .addPointsByRating();
	}

	
	public void doTutorial(long userId, long tutorialId) 
	        throws InstanceNotFoundException,
	                PermissionDeniedException, TutorialAlreadyDoneException
	{
	    User user = userDao.findById(userId);
		Tutorial tutorial = tutorialDao.findById(tutorialId);
		
		if(tutorial.isPending() || tutorial.isRejected())
        { throw new PermissionDeniedException(); }
		
		if(tutorial.getAuthor() == user)
		{ throw new PermissionDeniedException(); }
		
		List<Mark> tutorialMarks = markedTutorialDao.findTutorialMarks(userId, tutorialId);
		
        if(tutorialMarks.contains(Mark.DONE))
        { throw new TutorialAlreadyDoneException(tutorialId); }
        
        if(tutorialMarks.contains(Mark.TODO))
        { markedTutorialDao.removeMark(userId, tutorialId, Mark.TODO); }
        
        tutorial.done();
	    MarkedTutorial tutorialMark = 
	        new MarkedTutorial(user, tutorial, Mark.DONE);
	    markedTutorialDao.save(tutorialMark);
	    
	    new AssignPoints(tutorial)
	                .addPointsUserByDoTutorial(user);
	}

	@Transactional
	public void markTutorial(long userId, long tutorialId, Mark mark) 
	        throws InstanceNotFoundException, MarkAlreadyExistsException, 
	               PermissionDeniedException, TutorialAlreadyDoneException
	{
		User user = userDao.findById(userId);
		Tutorial tutorial = tutorialDao.findById(tutorialId);
		List<Mark> tutorialMarks = markedTutorialDao.findTutorialMarks(userId, tutorialId);
		
		// TODO: Create a dedicated table/entity for rated tutorials
		if (mark == Mark.DONE || mark == Mark.RATED)
		{ throw new IllegalArgumentException(); }
		
		if (tutorialMarks.contains(mark))
		{ throw new MarkAlreadyExistsException(tutorialId, mark); }
		
		if(tutorial.isPending() || tutorial.isRejected())
		{ throw new PermissionDeniedException(); }
		
		if (mark == Mark.TODO && tutorial.getAuthor() == user)
        { throw new PermissionDeniedException(); }
		
		if (mark == Mark.TODO && tutorialMarks.contains(Mark.DONE))
		{ throw new TutorialAlreadyDoneException(tutorialId); }
		
		MarkedTutorial tutorialMark = new MarkedTutorial(user,tutorial,mark);
		markedTutorialDao.save(tutorialMark);
	}
	
    public void unmarkTutorial(long userId, long tutorialId, Mark mark) 
        throws InstanceNotFoundException, MarkDoesNotExistException, 
                PermissionDeniedException
    {
        Tutorial tutorial = tutorialDao.findById(tutorialId);
        List<Mark> tutorialMarks = markedTutorialDao.findTutorialMarks(userId, tutorialId);

        // TODO: Create a dedicated table/entity for rated tutorials
        if (mark == Mark.DONE || mark == Mark.RATED)
        { throw new IllegalArgumentException(); }

        if (!tutorialMarks.contains(mark))
        { throw new MarkDoesNotExistException(tutorialId, mark); }

        if(!tutorial.isAccepted())
        { throw new PermissionDeniedException(); }

        markedTutorialDao.removeMark(userId, tutorialId, mark); 
    }
	
	/**
	 * Validating result: REJECTED, ACCEPTED, VALIDATED
	 * Commentary/Reason (mandatory except for "VALIDATED").
	 * @throws InvalidArgumentException 
	 * */
	@Transactional
	public void evaluateTutorial(long validatorId, long tutorialId,
	                            Status decision, String comment) 
	        throws InstanceNotFoundException, PermissionDeniedException, 
	                AlreadyEvaluatedException
    {		
	    User validator = userDao.findById(validatorId);
	    Tutorial tutorial = tutorialDao.findById(tutorialId);

	    if(decision == Status.PENDING)
	    { throw new IllegalArgumentException(); }
	    
        if(comment == null || comment.isEmpty())
        { throw new IllegalArgumentException(); }
	        
	    if(tutorial.isEvaluated())
	    { throw new AlreadyEvaluatedException(tutorialId); }

	    if(!tutorial.getDepartment().getLeaders().contains(validator))
	    { throw new PermissionDeniedException(); }
	        
	    if(tutorial.getAuthor() == validator)
	    { throw new PermissionDeniedException(); }
	        
	    tutorial.validate(validator, decision, comment);
	    
	    new AssignPoints(tutorial)
                    .addPointsByValidation(decision);
	    
	    tutorialDao.save(tutorial);
	    new AssignAward(tutorial, awardDao)
	                .addAwardByValidation();
	}
	
    public List<Tutorial> topTutorials(int count)
            throws InvalidArgumentException
    {
        if (count < 1 || count > MAX_COUNT)
        { throw new InvalidArgumentException(); }
        
        return tutorialDao.topTutorials(count);        
    }
  
    public Complaint complain(long userId, long tutorialId, Type type, String text)
            throws InstanceNotFoundException, PermissionDeniedException
    {
        User user = userDao.findById(userId);
        Tutorial tutorial = tutorialDao.findById(tutorialId);
        
        if (!tutorial.isAccepted())
        { throw new PermissionDeniedException(); }
        
        if(text == null || text.isEmpty())
        { throw new IllegalArgumentException(); }
        
        if (tutorial.getAuthor() == user)   // yeah, it would be weird ...
        { throw new PermissionDeniedException(); }
        
        Complaint complaint = new Complaint(Calendar.getInstance(),
                                            text, type,
                                            tutorial, user);
        complaintDao.save(complaint);
        
        return complaint;
    }
    
    // FIX
    @Transactional(readOnly = true)
    public double getRatedByUser(Long userId, Long tutId)
    {
        MarkedTutorial mk;
        try {
            mk = this.markedTutorialDao.findMark(userId, tutId, Mark.RATED);
            return (mk==null)?0:mk.getRating();
        } catch (InstanceNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return 0;
    }
}
